4 // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
6 // The use and distribution terms for this software are contained in the file
7 // named license.txt, which can be found in the root of this distribution.
8 // By using this software in any fashion, you are agreeing to be bound by the
9 // terms of this license.
11 // You must not remove this notice, or any other, from this software.
16 namespace Microsoft
.JScript
{
19 using System
.Collections
;
20 using System
.Diagnostics
;
21 using System
.Globalization
;
22 using System
.Reflection
;
23 using System
.Runtime
.InteropServices
.Expando
;
25 public class JSObject
: ScriptObject
, IEnumerable
, IExpando
{
26 private bool isASubClass
;
27 private IReflect subClassIR
;
28 private SimpleHashtable memberCache
;
29 internal bool noExpando
;
30 internal SimpleHashtable name_table
;
31 protected ArrayList field_table
;
32 internal JSObject outer_class_instance
;
36 this.noExpando
= false;
39 internal JSObject(ScriptObject parent
)
40 : this(parent
, true) {
43 internal JSObject(ScriptObject parent
, bool checkSubType
)
45 this.memberCache
= null;
46 this.isASubClass
= false;
47 this.subClassIR
= null;
49 Type subType
= Globals
.TypeRefs
.ToReferenceContext(this.GetType());
50 Debug
.Assert(subType
!= Typeob
.BuiltinFunction
);
51 if (subType
!= Typeob
.JSObject
){
52 this.isASubClass
= true;
53 this.subClassIR
= TypeReflector
.GetTypeReflectorFor(subType
);
56 Debug
.Assert(Globals
.TypeRefs
.ToReferenceContext(this.GetType()) == Typeob
.JSObject
);
57 this.noExpando
= this.isASubClass
;
58 this.name_table
= null;
59 this.field_table
= null;
60 this.outer_class_instance
= null;
63 internal JSObject(ScriptObject parent
, Type subType
)
65 this.memberCache
= null;
66 this.isASubClass
= false;
67 this.subClassIR
= null;
68 Debug
.Assert(subType
== this.GetType() || this.GetType() == typeof(BuiltinFunction
));
70 subType
= Globals
.TypeRefs
.ToReferenceContext(subType
);
71 if (subType
!= Typeob
.JSObject
){
72 this.isASubClass
= true;
73 this.subClassIR
= TypeReflector
.GetTypeReflectorFor(subType
);
75 this.noExpando
= this.isASubClass
;
76 this.name_table
= null;
77 this.field_table
= null;
80 public FieldInfo
AddField(String name
){
83 FieldInfo field
= (FieldInfo
)this.NameTable
[name
];
85 field
= new JSExpandoField(name
);
86 this.name_table
[name
] = field
;
87 this.field_table
.Add(field
);
92 MethodInfo IExpando
.AddMethod(String name
, Delegate method
){
96 PropertyInfo IExpando
.AddProperty(String name
){
100 internal override bool DeleteMember(String name
){
101 FieldInfo field
= (FieldInfo
)this.NameTable
[name
];
103 if (field
is JSExpandoField
){
104 field
.SetValue(this, Missing
.Value
);
105 this.name_table
.Remove(name
);
106 this.field_table
.Remove(field
);
108 }else if (field
is JSPrototypeField
){
109 field
.SetValue(this, Missing
.Value
);
113 }else if (this.parent
!= null)
114 return LateBinding
.DeleteMember(this.parent
, name
);
119 internal virtual String
GetClassName(){
124 [DebuggerStepThroughAttribute
]
125 [DebuggerHiddenAttribute
]
127 internal override Object
GetDefaultValue(PreferredType preferred_type
){
128 if (preferred_type
== PreferredType
.String
){
129 ScriptFunction toString
= this.GetMemberValue("toString") as ScriptFunction
;
130 if (toString
!= null){
131 Object result
= toString
.Call(new Object
[0], this);
132 if (result
== null) return result
;
133 IConvertible ic
= Convert
.GetIConvertible(result
);
134 if (ic
!= null && ic
.GetTypeCode() != TypeCode
.Object
) return result
;
136 ScriptFunction valueOf
= this.GetMemberValue("valueOf") as ScriptFunction
;
137 if (valueOf
!= null){
138 Object result
= valueOf
.Call(new Object
[0], this);
139 if (result
== null) return result
;
140 IConvertible ic
= Convert
.GetIConvertible(result
);
141 if (ic
!= null && ic
.GetTypeCode() != TypeCode
.Object
) return result
;
143 }else if (preferred_type
== PreferredType
.LocaleString
){
144 ScriptFunction toLocaleString
= this.GetMemberValue("toLocaleString") as ScriptFunction
;
145 if (toLocaleString
!= null){
146 return toLocaleString
.Call(new Object
[0], this);
149 if (preferred_type
== PreferredType
.Either
&& this is DateObject
)
150 return this.GetDefaultValue(PreferredType
.String
);
151 ScriptFunction valueOf
= this.GetMemberValue("valueOf") as ScriptFunction
;
152 if (valueOf
!= null){
153 Object result
= valueOf
.Call(new Object
[0], this);
154 if (result
== null) return result
;
155 IConvertible ic
= Convert
.GetIConvertible(result
);
156 if (ic
!= null && ic
.GetTypeCode() != TypeCode
.Object
) return result
;
158 ScriptFunction toString
= this.GetMemberValue("toString") as ScriptFunction
;
159 if (toString
!= null){
160 Object result
= toString
.Call(new Object
[0], this);
161 if (result
== null) return result
;
162 IConvertible ic
= Convert
.GetIConvertible(result
);
163 if (ic
!= null && ic
.GetTypeCode() != TypeCode
.Object
) return result
;
169 IEnumerator IEnumerable
.GetEnumerator(){
170 return ForIn
.JScriptGetEnumerator(this);
173 private static bool IsHiddenMember(MemberInfo mem
) {
174 // Members that are declared in super classes of JSObject are hidden except for those
176 Type mtype
= mem
.DeclaringType
;
177 if (mtype
== Typeob
.JSObject
|| mtype
== Typeob
.ScriptObject
||
178 (mtype
== Typeob
.ArrayWrapper
&& mem
.Name
!= "length"))
183 private MemberInfo
[] GetLocalMember(String name
, BindingFlags bindingAttr
, bool wrapMembers
){
184 MemberInfo
[] result
= null;
185 FieldInfo field
= this.name_table
== null ? null : (FieldInfo
)this.name_table
[name
];
186 if (field
== null && this.isASubClass
){
187 if (this.memberCache
!= null){
188 result
= (MemberInfo
[])this.memberCache
[name
];
189 if (result
!= null) return result
;
191 bindingAttr
&= ~BindingFlags
.NonPublic
; //Never expose non public fields of old style objects
192 result
= this.subClassIR
.GetMember(name
, bindingAttr
);
193 if (result
.Length
== 0)
194 result
= this.subClassIR
.GetMember(name
, (bindingAttr
&~BindingFlags
.Instance
)|BindingFlags
.Static
);
195 int n
= result
.Length
;
197 //Suppress any members that are declared in JSObject or earlier. But keep the ones in Object.
198 int hiddenMembers
= 0;
199 foreach (MemberInfo mem
in result
){
200 if (JSObject
.IsHiddenMember(mem
))
203 if (hiddenMembers
> 0 && !(n
== 1 && this is ObjectPrototype
&& name
== "ToString")){
204 MemberInfo
[] newResult
= new MemberInfo
[n
-hiddenMembers
];
206 foreach (MemberInfo mem
in result
){
207 if (!JSObject
.IsHiddenMember(mem
))
208 newResult
[j
++] = mem
;
213 if ((result
== null || result
.Length
== 0) && (bindingAttr
& BindingFlags
.Public
) != 0 && (bindingAttr
& BindingFlags
.Instance
) != 0){
214 BindingFlags flags
= (bindingAttr
& BindingFlags
.IgnoreCase
) | BindingFlags
.Public
| BindingFlags
.Instance
;
215 if (this is StringObject
)
216 result
= TypeReflector
.GetTypeReflectorFor(Typeob
.String
).GetMember(name
, flags
);
217 else if (this is NumberObject
)
218 result
= TypeReflector
.GetTypeReflectorFor(((NumberObject
)this).baseType
).GetMember(name
, flags
);
219 else if (this is BooleanObject
)
220 result
= TypeReflector
.GetTypeReflectorFor(Typeob
.Boolean
).GetMember(name
, flags
);
221 else if (this is StringConstructor
)
222 result
= TypeReflector
.GetTypeReflectorFor(Typeob
.String
).GetMember(name
, (flags
|BindingFlags
.Static
)&~BindingFlags
.Instance
);
223 else if (this is BooleanConstructor
)
224 result
= TypeReflector
.GetTypeReflectorFor(Typeob
.Boolean
).GetMember(name
, (flags
|BindingFlags
.Static
)&~BindingFlags
.Instance
);
225 else if (this is ArrayWrapper
)
226 result
= TypeReflector
.GetTypeReflectorFor(Typeob
.Array
).GetMember(name
, flags
);
228 if (result
!= null && result
.Length
> 0){
230 result
= ScriptObject
.WrapMembers(result
, this);
231 if (this.memberCache
== null) this.memberCache
= new SimpleHashtable(32);
232 this.memberCache
[name
] = result
;
236 if ((bindingAttr
&BindingFlags
.IgnoreCase
) != 0 && (result
== null || result
.Length
== 0)){
238 IDictionaryEnumerator e
= this.name_table
.GetEnumerator();
239 while (e
.MoveNext()){
240 if (String
.Compare(e
.Key
.ToString(), name
, StringComparison
.OrdinalIgnoreCase
) == 0){
241 field
= (FieldInfo
)e
.Value
;
247 return new MemberInfo
[]{field}
;
248 if (result
== null) result
= new MemberInfo
[0];
252 public override MemberInfo
[] GetMember(String name
, BindingFlags bindingAttr
){
253 return this.GetMember(name
, bindingAttr
, false);
256 private MemberInfo
[] GetMember(String name
, BindingFlags bindingAttr
, bool wrapMembers
){
257 MemberInfo
[] members
= this.GetLocalMember(name
, bindingAttr
, wrapMembers
);
258 if (members
.Length
> 0) return members
;
259 if (this.parent
!= null){
260 if (this.parent
is JSObject
){
261 members
= ((JSObject
)this.parent
).GetMember(name
, bindingAttr
, true);
264 members
= this.parent
.GetMember(name
, bindingAttr
);
265 foreach (MemberInfo mem
in members
){
266 if (mem
.MemberType
== MemberTypes
.Field
){
267 FieldInfo field
= (FieldInfo
)mem
;
268 JSMemberField mfield
= mem
as JSMemberField
;
269 if (mfield
!= null){ //This can only happen when running in the Evaluator
270 if (!mfield
.IsStatic
){
271 JSGlobalField gfield
= new JSGlobalField(this, name
, mfield
.value, FieldAttributes
.Public
);
272 this.NameTable
[name
] = gfield
;
273 this.field_table
.Add(gfield
);
277 field
= new JSPrototypeField(this.parent
, (FieldInfo
)mem
);
278 if (!this.noExpando
){
279 this.NameTable
[name
] = field
;
280 this.field_table
.Add(field
);
283 return new MemberInfo
[]{field}
;
285 if (!this.noExpando
){
286 if (mem
.MemberType
== MemberTypes
.Method
){
287 FieldInfo field
= new JSPrototypeField(this.parent
,
288 new JSGlobalField(this, name
,
289 LateBinding
.GetMemberValue(this.parent
, name
, null, members
),
290 FieldAttributes
.Public
|FieldAttributes
.InitOnly
));
291 this.NameTable
[name
] = field
;
292 this.field_table
.Add(field
);
293 return new MemberInfo
[]{field}
;
298 return ScriptObject
.WrapMembers(members
, this.parent
);
302 return new MemberInfo
[0];
305 public override MemberInfo
[] GetMembers(BindingFlags bindingAttr
){
306 MemberInfoList mems
= new MemberInfoList();
307 SimpleHashtable uniqueMems
= new SimpleHashtable(32);
309 if (!this.noExpando
&& this.field_table
!= null){ //Add any expando properties
310 IEnumerator enu
= this.field_table
.GetEnumerator();
311 while (enu
.MoveNext()){
312 FieldInfo field
= (FieldInfo
)enu
.Current
;
314 uniqueMems
[field
.Name
] = field
;
318 //Add the public members of the built-in objects if they don't already exist
319 if (this.isASubClass
){
320 MemberInfo
[] ilMembers
= this.GetType().GetMembers(bindingAttr
& ~BindingFlags
.NonPublic
); //Never expose non public members of old style objects
321 for (int i
= 0, n
= ilMembers
.Length
; i
< n
; i
++){
322 MemberInfo ilMem
= ilMembers
[i
];
324 //Hide any infrastructure stuff in JSObject
325 if (!ilMem
.DeclaringType
.IsAssignableFrom(Typeob
.JSObject
) && uniqueMems
[ilMem
.Name
] == null){
326 MethodInfo method
= ilMem
as MethodInfo
;
327 if (method
== null || !method
.IsSpecialName
){
329 uniqueMems
[ilMem
.Name
] = ilMem
;
335 //Add parent members if they don't already exist
336 if (this.parent
!= null){
337 SimpleHashtable cache
= this.parent
.wrappedMemberCache
;
339 cache
= this.parent
.wrappedMemberCache
= new SimpleHashtable(8);
340 MemberInfo
[] parentMems
= ScriptObject
.WrapMembers(((IReflect
)this.parent
).GetMembers(bindingAttr
& ~BindingFlags
.NonPublic
), this.parent
, cache
);
341 for(int i
= 0, n
= parentMems
.Length
; i
< n
; i
++){
342 MemberInfo parentMem
= parentMems
[i
];
343 if(uniqueMems
[parentMem
.Name
] == null){
345 //uniqueMems[parentMem.Name] = parentMem; //No need to add to lookup table - no one else will be looking.
350 return mems
.ToArray();
353 internal override void GetPropertyEnumerator(ArrayList enums
, ArrayList objects
){
354 if (this.field_table
== null) this.field_table
= new ArrayList();
355 enums
.Add(new ListEnumerator(this.field_table
));
357 if (this.parent
!= null)
358 this.parent
.GetPropertyEnumerator(enums
, objects
);
361 internal override Object
GetValueAtIndex(uint index
){ //used by array functions
362 String name
= System
.Convert
.ToString(index
, CultureInfo
.InvariantCulture
);
363 //Do not defer to to routine below, since Array objects override it and could call back to this routine
364 FieldInfo field
= (FieldInfo
)(this.NameTable
[name
]);
366 return field
.GetValue(this);
368 Object result
= null;
369 if (this.parent
!= null)
370 result
= this.parent
.GetMemberValue(name
);
372 result
= Missing
.Value
;
373 if (this is StringObject
&& result
== Missing
.Value
){
374 String str
= ((StringObject
)this).value;
375 if (index
< str
.Length
)
376 return str
[(int)index
];
383 [DebuggerStepThroughAttribute
]
384 [DebuggerHiddenAttribute
]
386 internal override Object
GetMemberValue(String name
){
387 FieldInfo field
= (FieldInfo
)this.NameTable
[name
];
388 if (field
== null && this.isASubClass
){
389 field
= this.subClassIR
.GetField(name
, BindingFlags
.Instance
|BindingFlags
.Static
|BindingFlags
.Public
);
391 if (field
.DeclaringType
== Typeob
.ScriptObject
) return Missing
.Value
;
393 PropertyInfo prop
= this.subClassIR
.GetProperty(name
, BindingFlags
.Instance
|BindingFlags
.Public
);
394 if (prop
!= null && !prop
.DeclaringType
.IsAssignableFrom(Typeob
.JSObject
))
395 return JSProperty
.GetGetMethod(prop
, false).Invoke(this, BindingFlags
.SuppressChangeType
, null, null, null);
397 MethodInfo method
= this.subClassIR
.GetMethod(name
, BindingFlags
.Public
|BindingFlags
.Static
);
399 Type dt
= method
.DeclaringType
;
400 if (dt
!= Typeob
.JSObject
&& dt
!= Typeob
.ScriptObject
&& dt
!= Typeob
.Object
)
401 return new BuiltinFunction(this, method
);
403 }catch(AmbiguousMatchException
){}
407 return field
.GetValue(this);
408 if (this.parent
!= null)
409 return this.parent
.GetMemberValue(name
);
410 return Missing
.Value
;
413 internal SimpleHashtable NameTable
{
415 SimpleHashtable result
= this.name_table
;
417 this.name_table
= result
= new SimpleHashtable(16);
418 this.field_table
= new ArrayList();
424 void IExpando
.RemoveMember(MemberInfo m
){
425 this.DeleteMember(m
.Name
);
429 [DebuggerStepThroughAttribute
]
430 [DebuggerHiddenAttribute
]
432 internal override void SetMemberValue(String name
, Object
value){
433 this.SetMemberValue2(name
, value);
436 public void SetMemberValue2(String name
, Object
value){
437 FieldInfo field
= (FieldInfo
)this.NameTable
[name
];
438 if (field
== null && this.isASubClass
)
439 field
= this.GetType().GetField(name
);
443 field
= new JSExpandoField(name
);
444 this.name_table
[name
] = field
;
445 this.field_table
.Add(field
);
447 if (!field
.IsInitOnly
&& !field
.IsLiteral
)
448 field
.SetValue(this, value);
451 internal override void SetValueAtIndex(uint index
, Object
value){
452 this.SetMemberValue(System
.Convert
.ToString(index
, CultureInfo
.InvariantCulture
), value);
455 internal virtual void SwapValues(uint left
, uint right
){
456 String left_name
= System
.Convert
.ToString(left
, CultureInfo
.InvariantCulture
);
457 String right_name
= System
.Convert
.ToString(right
, CultureInfo
.InvariantCulture
);
458 FieldInfo left_field
= (FieldInfo
)(this.NameTable
[left_name
]);
459 FieldInfo right_field
= (FieldInfo
)(this.name_table
[right_name
]);
460 if (left_field
== null)
461 if (right_field
== null)
464 this.name_table
[left_name
] = right_field
;
465 this.name_table
.Remove(right_name
);
467 else if (right_field
== null){
468 this.name_table
[right_name
] = left_field
;
469 this.name_table
.Remove(left_name
);
471 this.name_table
[left_name
] = right_field
;
472 this.name_table
[right_name
] = left_field
;
476 public override String
ToString(){
477 return Convert
.ToString(this);